/***************************************************************
 * Name:      AvmUpdateCreatorMain.cpp
 * Purpose:   Code for Application Frame
 * Author:    Ke Zhonghua (ke.zhonghua@o-film.com)
 * Created:   2018-07-03
 * Copyright: Ke Zhonghua ()
 * License:
 **************************************************************/

#ifdef WX_PRECOMP
#include "wx_pch.h"
#endif

#ifdef __BORLANDC__
#pragma hdrstop
#endif //__BORLANDC__

#include "AvmUpdateCreatorMain.h"
#include "flash_tool.h"
#include <wx/xrc/xmlres.h>
#include "GenericSettingsDlg.h"
#include "AppConfig.h"

//helper functions
enum wxbuildinfoformat
{
    short_f, long_f
};

wxString wxbuildinfo(wxbuildinfoformat format)
{
    wxString wxbuild(wxVERSION_STRING);

    if (format == long_f )
    {
#if defined(__WXMSW__)
        wxbuild << _T("-Windows");
#elif defined(__WXMAC__)
        wxbuild << _T("-Mac");
#elif defined(__UNIX__)
        wxbuild << _T("-Linux");
#endif

#if wxUSE_UNICODE
        wxbuild << _T("-Unicode build");
#else
        wxbuild << _T("-ANSI build");
#endif // wxUSE_UNICODE
    }

    return wxbuild;
}

wxString AvmUpdateCreatorFrame::AutoMountCmds[4] = { "mkdir /tmp/rootfs ; mount /dev/mmcblk0p2 /tmp/rootfs; ", "mkdir /tmp/opt ; mount /dev/mmcblk0p3 /tmp/opt;", "mkdir ${rootfs} ; mount /dev/mmcblk0p2 ${rootfs}; ", "mkdir ${opt} ; mount /dev/mmcblk0p3 ${opt};"};

BEGIN_EVENT_TABLE(AvmUpdateCreatorFrame, wxFrame)
    EVT_CLOSE(AvmUpdateCreatorFrame::OnClose)
    EVT_MENU(XRCID("open_update_bin"), AvmUpdateCreatorFrame::OnOpenUpdatebin)
    EVT_MENU(wxID_EXIT, AvmUpdateCreatorFrame::OnQuit)
    EVT_MENU(idMenuAbout, AvmUpdateCreatorFrame::OnAbout)

    EVT_MENU(XRCID("remove_all_items"), AvmUpdateCreatorFrame::OnMenuRemoveAllItems)
    EVT_MENU(XRCID("generic_settings"), AvmUpdateCreatorFrame::OnMenuGenericSettings)

    EVT_MENU(XRCID("dump_cmds"), AvmUpdateCreatorFrame::OnMenuDumpCmds)
    EVT_MENU(XRCID("load_from_scripts"), AvmUpdateCreatorFrame::OnMenuLoadCmds)

    EVT_BUTTON(XRCID("btn_add_item"), AvmUpdateCreatorFrame::OnBtnAddNewItem)
    EVT_BUTTON(XRCID("btn_execute"), AvmUpdateCreatorFrame::OnBtnExecute)
    EVT_BUTTON(XRCID("btn_sel_outputdir"), AvmUpdateCreatorFrame::OnBtnBrowseOutputdir)
END_EVENT_TABLE()

AvmUpdateCreatorFrame::AvmUpdateCreatorFrame(wxFrame *frame, const wxString& title)
//   : wxFrame(frame, -1, title)
    : updateitem_sizeritem(NULL), updateitems_sizer(NULL), update_items_panel(NULL)
{
    if(wxXmlResource::Get()->LoadFrame(this, NULL, wxT("avmcreator_frame")) == true)
    {
        SetMinClientSize(wxSize(800, 600));
        InitUI();
        AppConfig::Get();
    }
    else
    {
        wxMessageBox(_("Initialize main frame failed!"));
        return;
    }
#if wxUSE_MENUS
    // create a menu bar
    /*wxMenuBar* mbar = new wxMenuBar();
    wxMenu* fileMenu = new wxMenu(_T(""));
    fileMenu->Append(idMenuQuit, _("&Quit\tAlt-F4"), _("Quit the application"));
    mbar->Append(fileMenu, _("&File"));

    wxMenu* helpMenu = new wxMenu(_T(""));
    helpMenu->Append(idMenuAbout, _("&About\tF1"), _("Show info about this application"));
    mbar->Append(helpMenu, _("&Help"));
    */
    wxMenuBar* mbar = wxXmlResource::Get()->LoadMenuBar("main_menu");
    SetMenuBar(mbar);
#endif // wxUSE_MENUS

#if wxUSE_STATUSBAR
    // create a status bar with some information about the used wxWidgets version
    CreateStatusBar(2);
    SetStatusText(_("Thanks for using"),0);
    SetStatusText(wxT("Powered by ") + wxbuildinfo(short_f), 1);
#endif // wxUSE_STATUSBAR

}


AvmUpdateCreatorFrame::~AvmUpdateCreatorFrame()
{
}

void AvmUpdateCreatorFrame::OnClose(wxCloseEvent &event)
{
    Destroy();
}

void AvmUpdateCreatorFrame::OnQuit(wxCommandEvent &event)
{
    Destroy();
}

void AvmUpdateCreatorFrame::OnAbout(wxCommandEvent &event)
{
    wxString msg = wxbuildinfo(long_f);
    wxMessageBox(msg, _("Welcome to..."));
}

void AvmUpdateCreatorFrame::InitUI()
{
    wxSizer* top_sizer = this->GetSizer();
    updateitem_sizeritem = top_sizer->GetItemById(XRCID("update_items_sizeritem"));
    if(updateitem_sizeritem != NULL)
    {
        wxSizer *update_sizer = updateitem_sizeritem->GetSizer();
        update_items_panel = new wxScrolledWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHSCROLL|wxVSCROLL);
        update_sizer->Add(update_items_panel, 1, wxEXPAND);
        updateitems_sizer = new wxBoxSizer(wxVERTICAL);
        update_items_panel->SetSizer(updateitems_sizer);
        update_items_panel->SetScrollbars(1, 1, 1, 1);
    }
    txt_output = XRCCTRL(*this, "txt_output_file", wxTextCtrl);
    btn_sel_outputdir = XRCCTRL(*this, "btn_sel_outputdir", wxButton);
    btn_add = XRCCTRL(*this, "btn_add_item", wxButton);


    if(txt_output)
    {
        wxString cur_dir = wxGetCwd();
        cur_output_file = cur_dir+wxFileName::GetPathSeparator()+wxT("avmupdate.bin");
        txt_output->SetValue(cur_output_file);
        if(wxFileName::Exists(cur_output_file))
            Load(cur_output_file);
    }
    /*wxMediaCtrl* media_player = new wxMediaCtrl(this, wxID_ANY);
    media_player->SetVolume(1.0);
    media_player->Load(wxT("rc/music.mp3"));
    media_player->Play();*/

    Connect(AvmUpdateItemPanel::Evt_DeleteItem, (wxObjectEventFunction)&AvmUpdateCreatorFrame::OnDeleteItem);
    top_sizer->Layout();
    top_sizer->Fit(this);
}

void AvmUpdateCreatorFrame::OnBtnAddNewItem(wxCommandEvent& event)
{
    if(updateitems_sizer)
    {
        AvmUpdateItemPanel* panel = new AvmUpdateItemPanel(update_items_panel, true, txt_output->GetValue());
        //wxTextCtrl *panel = new wxTextCtrl(updateitem_sizeritem->GetWindow(), wxID_ANY, wxT("sssssss"));
        if(panel != NULL)
        {
            if(update_items.size() > 0)
                panel->SetUpdateItemType((*(update_items.end() - 1))->GetUpdateItemType());
            update_items.push_back(panel);
            updateitems_sizer->Add(panel, 0, wxTOP|wxEXPAND, 5);
            panel->SetNewFlag(true);
            this->GetSizer()->Layout();
            panel->FitInside();
        }
    }
}

void AvmUpdateCreatorFrame::FreezeAllUpdateItems(bool freeze)
{
    update_items_panel->Enable(!freeze);
    if(btn_add)
    {
        btn_add->Enable(!freeze);
    }
    if(btn_sel_outputdir)
    {
        btn_sel_outputdir->Enable(!freeze);
    }
}

uint32_t AvmUpdateCreatorFrame::CheckIfMountNeeded()
{
    uint32_t mount_flag = 0;
    uint32_t mount_flag2 = 0xFF;
    UpdateItemsList::const_iterator it = update_items.begin();
    UpdateItemsList::const_iterator end = update_items.end();
    wxString str_tmp;
    while(it != end)
    {
        switch((*it)->GetUpdateItemType())
        {
        case SECTION_COMMAND:
            str_tmp = (*it)->GetCommandLine();
            break;
        case SECTION_FS_FILE:
            str_tmp = (*it)->GetTargetPath();
            break;
        default:
            str_tmp = wxEmptyString;
        }
        if(str_tmp.Find("${rootfs}") >= 0)
            mount_flag |= 2;
        if(str_tmp.Find("${opt}") >= 0)
            mount_flag |= 4;
        if(str_tmp.Find(AutoMountCmds[2]) >= 0)
        {
            mount_flag2 &= (~2);
        }
        if(str_tmp.Find(AutoMountCmds[3]) >= 0)
        {
            mount_flag2 &= (~4);
        }

        if((mount_flag & 2) && (mount_flag & 4))
            break;
        ++it;
    }
    return mount_flag & mount_flag2;
}

bool AvmUpdateCreatorFrame::InsertMountCommand(uint32_t mount_need)
{
    wxString output_bin = txt_output->GetValue();
    wxString cmd = wxEmptyString;
    if(mount_need & 2)
    {
        cmd.Append(AutoMountCmds[0]);
    }
    if(mount_need & 4)
    {
        cmd.Append(AutoMountCmds[1]);
    }
    if(cmd.Length() > 0)
    {
        int err = build_section(NULL, "command line", output_bin.c_str(), -1, NULL, cmd.c_str());
        if(err == 0)
            return true;
        return false;
    }
    return true;
}

void AvmUpdateCreatorFrame::InsertRmCommands()
{
    wxString output = txt_output->GetValue();
    UpdateItemsList::const_iterator it = update_items.begin();
    UpdateItemsList::const_iterator end = update_items.end();
    while(it != end)
    {
        AvmUpdateItemPanel* panel = *it;
        if(panel->GetUpdateItemType() == SECTION_FS_FILE && panel->GetNewFlag())
        {
            wxString path = panel->GetTargetPath();
            wxString cmd = wxString::Format(wxT("rm %s"), path);
            AvmUpdateItemPanel* new_panel = new AvmUpdateItemPanel(update_items_panel);
            if(new_panel)
            {
                new_panel->SetCommandLine(cmd);
                new_panel->SetUpdateItemType(SECTION_COMMAND);
                updateitems_sizer->Add(new_panel, 0, wxTOP|wxEXPAND, 5);
                new_panel->AppendUpdateItem(output);
            }

        }
        ++it;
    }
    Layout();
}

bool AvmUpdateCreatorFrame::AppendNewUpdateItem(AvmUpdateItemPanel* panel)
{
    return panel->AppendUpdateItem(txt_output->GetValue());
}

void AvmUpdateCreatorFrame::OnBtnExecute(wxCommandEvent& event)
{
    if(AppConfig::Get().IsOverwriteOutputfile())
    {
        if(wxFileName::Exists(txt_output->GetValue()))
        {
            if(wxMessageBox(_("According to your configure, the output file would be overwriten, are you sure?"), _("Attention"), wxYES_NO|wxCENTER) == wxNO)
            {
                return;
            }
            wxRemoveFile(txt_output->GetValue());
        }
    }
    FreezeAllUpdateItems(true);

    //1. 先检查所有的项，找到是否有文件系统类型更新
    uint32_t mount_need = CheckIfMountNeeded();
    //2. 如果有满足条件1的项，则需插入对应的挂载操作
    if(InsertMountCommand(mount_need) != true)
    {
        wxMessageBox(_("Automatly insert mount commands failed!"));
        return;
    }
    //3. 执行文件预删除
//    InsertRmCommands();
    //4. 依次执行新加项
    UpdateItemsList::const_iterator it = update_items.begin();
    UpdateItemsList::const_iterator end = update_items.end();
    while(it != end)
    {
        if(!AppendNewUpdateItem(*it))
        {
            wxMessageBox(_("Append new update item failed, please retry!"));
            break;
        }
        ++it;
    }
    if(it == end)
    {
        LoadOutputFile(txt_output->GetValue());
        wxMessageBox(_("Build update package successfully."));
    }
    else
    {
        wxMessageBox(_("Build update package failed, please check it."));
    }
    //FreezeAllUpdateItems(false);
}

void AvmUpdateCreatorFrame::OnBtnBrowseOutputdir(wxCommandEvent& event)
{
    if(txt_output != NULL)
    {
        wxFileName cur_path = txt_output->GetValue();
        wxDirDialog dlg(this, _("Select output directory"), cur_path.GetPath(), wxDD_DEFAULT_STYLE|wxDD_DIR_MUST_EXIST);
        if(dlg.ShowModal() == wxID_OK)
        {
            txt_output->SetValue(dlg.GetPath() + wxFileName::GetPathSeparator() + wxT("avmupdate.bin"));
            DealOutputDirChange();
        }
    }
}

void AvmUpdateCreatorFrame::DealOutputDirChange()
{
    wxString new_path = txt_output->GetValue();
    if(new_path.Cmp(cur_output_file))
    {
        LoadOutputFile(new_path);
        cur_output_file = new_path;
    }
}

bool AvmUpdateCreatorFrame::LoadOutputFile(const wxString& file)
{
    //1.先测试目标文件是否存在
    if(wxFileName::Exists(file))
    {
        //2. 如果现在文件已经存在，则需要删除所有项
        RemoveAllItems();
        //3. 删除所有现在项之后，还得加入原文件存在的项
        return Load(file);
    }
    else
    {
        //2. 如果文件不存在，则保留所有现存项，但需将所有现存项都标记为新项
        MarkAllItemsNew();
        return true;
    }
}

void AvmUpdateCreatorFrame::RemoveAllItems()
{
    for(size_t idx = 0; idx < update_items.size(); ++idx)
    {
        update_items[idx]->Destroy();
    }
    update_items.clear();
    Layout();
    return;
}

void AvmUpdateCreatorFrame::MarkAllItemsNew()
{
    UpdateItemsList::iterator it = update_items.begin();
    UpdateItemsList::iterator end = update_items.end();
    while(it != end)
    {
        (*it)->SetNewFlag(true);
        it++;
    }
}

bool AvmUpdateCreatorFrame::Load(const wxString& file)
{
    const char* input_file = file.c_str();
    if(check_binary_file(input_file) == 0)
    {
        wxFile fi(file, wxFile::read);
        if(!fi.IsOpened())
        {
            wxMessageBox(_("Verify existed update binary failed, please check it or remove it and retry again!"));
            return false;
        }
        //UpdateSectionHeaders sections;
        //SectionHeader item;
        wxFileOffset pos = 0;
        while(pos < fi.Length() && pos != wxInvalidOffset)
        {
            pos = AppendUpdateItemFromFile(fi);
        }
        if(pos < fi.Length())
        {
            return false;
        }
        //InsertUpdateItems(sections);
    }
    else
    {
        wxMessageBox(_("Verify existed update binary failed, please check it or remove it and retry again!"));
        return false;
    }
    return true;
}

wxFileOffset AvmUpdateCreatorFrame::AppendUpdateItemFromFile(wxFile& fi)
{
    SectionHeader item;
    if(fi.Read(&item, sizeof(item)) != sizeof(item))
    {
        wxMessageBox(_("Read input file failed!"));
        return wxInvalidOffset;
    }
    switch(item.flag)
    {
    case SECTION_COMMAND:
        return AppendCmdAndFsfileFromFile(fi, item);
    case SECTION_FS_FILE:
        return AppendCmdAndFsfileFromFile(fi, item);
    case SECTION_RAW_DATA:
        return AppendRawitemFromFile(fi, item);
    default:
        break;
    }
    return wxInvalidOffset;
}

wxFileOffset AvmUpdateCreatorFrame::AppendCmdAndFsfileFromFile(wxFile& fi, SectionHeader& item)
{
    uint16_t cmd_flag;
    uint16_t cmd_len;
    wxFileOffset offset = wxInvalidOffset;
    if(fi.Read(&cmd_flag, sizeof(cmd_flag)) == sizeof(cmd_flag)
            && fi.Read(&cmd_len, sizeof(cmd_len)) == sizeof(cmd_len))
    {
        char* str = (char*)malloc(cmd_len);
        if(str != NULL)
        {
            if(fi.Read(str, cmd_len) == cmd_len)
            {
                AvmUpdateItemPanel* new_panel = new AvmUpdateItemPanel(update_items_panel, false);
                if(new_panel != NULL)
                {
                    update_items.push_back(new_panel);
                    if(item.flag == SECTION_COMMAND)
                    {
                        new_panel->SetCommandLine(str);
                        new_panel->SetUpdateItemType(SECTION_COMMAND);
                    }
                    else if(item.flag == SECTION_FS_FILE)
                    {
                        new_panel->SetTargetPath(str);
                        new_panel->SetUpdateItemType(SECTION_FS_FILE);
                        new_panel->SetSourceBinaryPath(txt_output->GetValue());
                        new_panel->SetSourceBinaryOffset(fi.Tell());
                        new_panel->SetSourceBinarySize(item.data_size - sizeof(uint32_t) - cmd_len);
                        fi.Seek(item.data_size - sizeof(uint32_t) - cmd_len, wxFromCurrent);
                    }
                    new_panel->SetNewFlag(false);
                    updateitems_sizer->Add(new_panel, 0, wxTOP|wxEXPAND, 5);
                    this->GetSizer()->Layout();
                    new_panel->FitInside();
                    offset = fi.Tell();
                }
            }
            free(str);
        }
    }
    return offset;
}

wxFileOffset AvmUpdateCreatorFrame::AppendFsfileItemFromFile(wxFile& fi, SectionHeader& item)
{
    return wxInvalidOffset;
}

wxFileOffset AvmUpdateCreatorFrame::AppendRawitemFromFile(wxFile& fi, SectionHeader& item)
{
    AvmUpdateItemPanel* new_panel = new AvmUpdateItemPanel(update_items_panel, false);
    if(new_panel != NULL)
    {
        update_items.push_back(new_panel);
        new_panel->SetEmmcPosition(item.blk_offset);
        new_panel->SetUpdateItemType(SECTION_RAW_DATA);
        new_panel->SetNewFlag(false);
        updateitems_sizer->Add(new_panel, 0, wxTOP|wxEXPAND, 5);
        this->GetSizer()->Layout();
        new_panel->FitInside();
        new_panel->SetSourceBinaryPath(txt_output->GetValue());
        new_panel->SetSourceBinaryOffset(fi.Tell());
        new_panel->SetSourceBinarySize(item.data_size);
        return fi.Seek(item.data_size, wxFromCurrent);
    }
    return wxInvalidOffset;
}

int AvmUpdateCreatorFrame::InsertUpdateItems(const UpdateSectionHeaders& items)
{
    int added = 0;
    UpdateSectionHeaders::const_iterator it = items.begin();
    UpdateSectionHeaders::const_iterator end = items.end();
    while(it != end)
    {
        AvmUpdateItemPanel* new_panel = new AvmUpdateItemPanel(update_items_panel, false);
        if(new_panel != NULL)
        {
            new_panel->UpdateInformation(*it);
            update_items.push_back(new_panel);
            updateitems_sizer->Add(new_panel, 0, wxTOP|wxEXPAND, 5);
            this->GetSizer()->Layout();
            new_panel->FitInside();
            ++added;
        }
    }
    return added;
}

bool AvmUpdateCreatorFrame::RemoveItem(AvmUpdateItemPanel* panel)
{
    UpdateItemsList::iterator it = update_items.begin();
    UpdateItemsList::iterator end = update_items.end();
    while(it != end)
    {
        if(*it == panel)
        {
            update_items_panel->RemoveChild(panel);
            panel->Destroy();
            update_items.erase(it);
            return true;
        }
        ++it;
    }
    return false;
}

void AvmUpdateCreatorFrame::OnDeleteItem(wxCommandEvent& event)
{
    AvmUpdateItemPanel* panel = (AvmUpdateItemPanel*)event.GetClientData();
    RemoveItem(panel);
    Layout();
}

void AvmUpdateCreatorFrame::OnMenuRemoveAllItems(wxCommandEvent& event)
{
    RemoveAllItems();
}

void AvmUpdateCreatorFrame::OnMenuGenericSettings(wxCommandEvent& event)
{
    GenericSettingsDlg dlg(this);
    dlg.ShowModal();
}

void AvmUpdateCreatorFrame::OnOpenUpdatebin(wxCommandEvent& event)
{
    RemoveAllItems();

    wxFileDialog dlg(this, _("Select update file ..."), wxEmptyString, wxEmptyString, wxT("All files (*)|*"), wxFD_FILE_MUST_EXIST);
    if(dlg.ShowModal() == wxID_OK)
    {
        wxString path = dlg.GetPath();
        if(txt_output != NULL)
        {
            txt_output->SetValue(path);
            DealOutputDirChange();
        }
    }
}

void AvmUpdateCreatorFrame::OnMenuDumpCmds(wxCommandEvent& event)
{
    if(update_items.size() > 0)
    {
        wxDirDialog dlg(this, _("Select directory ..."), wxEmptyString, wxDD_DEFAULT_STYLE);
        if(dlg.ShowModal() == wxID_OK)
        {
            wxString path = dlg.GetPath();
            DumpMakeCommands(path);
        }
    }
}

bool AvmUpdateCreatorFrame::DumpMakeCommands(const wxString& dir)
{
    UpdateItemsList::iterator it = update_items.begin();
    UpdateItemsList::iterator end = update_items.end();

    wxString cmd_file = dir + "/cmd.sh";
    wxRemove(cmd_file);
    while(it != end)
    {
        if((*it)->DumpMakeCommand(cmd_file) != true)
        {
            wxMessageBox(wxString::Format(_("Make build command to %s FAILED!"), dir));
            return false;
        }
        it++;
    }
    wxMessageBox(wxString::Format(_("Make build command to %s successfully!"), dir));
    return true;
}

void AvmUpdateCreatorFrame::OnMenuLoadCmds(wxCommandEvent& event)
{
    wxMessageBox(_("This function has not been implemented so far, please wait a new version."));
}
